﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace N2.Workflow
{
	using Collections;
	using Definitions;
	using Items;
	using Persistence;
	using Persistence.Finder;
	using Web;

	sealed partial class WorkflowProvider
	{
		#region Fields
		//external references
		readonly IDefinitionManager definitions;
		readonly IItemFinder finder;
		readonly IPersister persister;
		readonly IHost host;

		IDictionary<int, Workflow> m_item2WorkflowCache;
		#endregion Fields
		
		#region Constructor

		public WorkflowProvider(
			IDefinitionManager definitions,
			IItemFinder finder,
			IPersister persister,
			IHost host)
		{
			this.definitions = definitions;
			this.finder = finder;
			this.persister = persister;
			this.host = host;

			this.m_item2WorkflowCache = new Dictionary<int, Workflow>();
		}

		#endregion Constructor

		#region Properties

		ContentItem Root {
			get { return this.persister.Get(this.host.CurrentSite.RootItemID); }
		}

		WorkflowRepository m_repo;
		internal WorkflowRepository Repository {
			get { return this.m_repo
				?? (this.m_repo = this.GetWorkflowRepository()); }
		}

		#endregion Properties

		#region Methods

		WorkflowRepository GetWorkflowRepository()
		{
			return this.Root.GetOrFindOrCreateChild<WorkflowRepository>(
				"WorkflowRepository",
				_item => {
					_item.Title = "Workflow Repository";
					return _item;
				});
		}

		Workflow GetDefinition(ContentItem item)
		{
			if (this.m_item2WorkflowCache.ContainsKey(item.ID)) {
				return this.m_item2WorkflowCache[item.ID];
			} else {
				var _wf = new WorkflowDefinitionFinder(this.Repository).FindWorkflow(item);

				//naive cache
				if (null != _wf) {
					this.m_item2WorkflowCache.Add(item.ID, _wf);
				}
				
				return _wf;
			}
		}

		ActionDefinition ResolveActionForItem(ContentItem item, string actionName)
		{
//TODO avoid 'failed to lazily initialize a collection, no session or session was closed' exception
			var _wf = this.persister.Get<Workflow>( this.GetDefinition(item).ID);

			ActionDefinition _action = (
				from _state in _wf.Items
				from _act in _state.Items
				where string.Equals(_act.Name, actionName, StringComparison.OrdinalIgnoreCase)
				select _act
			).FirstOrDefault();

			return _action;
		}

		ItemState PerformActionInternal(
			ContentItem item,
			ActionArguments argument)
		{
			if (argument.Mode == ActionArguments.ExecutionMode.ByName) {
				var _arg = argument;
				_arg.Action = this.ResolveActionForItem(item, _arg.ActionName);
			}

			IActionDefinition _action = argument.Action;

			if (null == _action) {
				throw new ArgumentException(
					"Cannot find a definition for requested action to execute",
					"argument.Action");
			}
			
			IItemState _currentState = this.GetCurrentState(item);

			if (!IsActionValidForState(_currentState.Definition, _action)) {
				throw new ArgumentException(
					string.Format(
						"Requested action '{0}' cannot be performed from state '{1}'",
						_action.Name,
						_currentState.Definition.Name),
					"argument.Action");
			}

			ItemState _newState =
				argument.ItemStateCandidate
				?? (ItemState)this.definitions.CreateInstance(
					_action.LeadsTo.StateType
						?? typeof(ItemState),
					item);
			
			bool _stateModified = false;

			_stateModified |= AssignPhysicalProperties(_newState, _currentState, _action);

			if (null == argument.ItemStateCandidate) {
				if (argument.Mode == ActionArguments.ExecutionMode.ByState) {
					_stateModified |= TransferCommonPropertiesFromTransientState(
						argument.State,
						_newState);
				} else if (argument.Mode == ActionArguments.ExecutionMode.ByName) {
					_stateModified |= AssignMatchingPropertiesFromAnonymousSource(
						argument.Values,
						_newState);
				}
			}

			if (!item.Children.Contains(_newState)) {
				_newState.AddTo(item);
				_stateModified = true;
			}

			//Expire item if it is at a final transition
			if (!_action.LeadsTo.Actions.Any()) {
				item.Expires = DateTime.Today.AddDays(1);
				_stateModified = true;
			}

			if (!_newState.IsPersistent() || _stateModified) {
//TODO move to ItemState as a default .Name property value
				if (string.IsNullOrEmpty(_newState.Name)) {
					_newState.Name = _newState.Action.Name
						+ "_"
						+ _newState.Definition.Name
						+ "_"
						+ DateTime.Now.Ticks.ToString();
				}

				this.PersistItemState(item, _newState);
			}
			
			return this.GetCurrentState(item) as ItemState;
		}
		
		ItemState GetCurrentState(ContentItem item)
		{
			var _detail = item.GetDetail("_CurrentState");
			var _cs = _detail as ItemState;

			if (null != _cs) {
				this.FixLinks(_cs);
				return _cs;
			}
			if (null != _detail) {
				//try to fix broken item link
				item.Details.Remove("_CurrentState");
			}

			var _lastGoodState = this.FindLastGoodState(item);
			if (null != _lastGoodState) {
				AssignCurrentState(item, _lastGoodState);
				this.persister.Save(item);
				return _lastGoodState;
			}

			var _initialState = this.CreateInitialState(item);

			if (null != _initialState) {
				using (var _tx = this.persister.Repository.BeginTransaction()) {
					this.persister.Save(_initialState);

					AssignCurrentState(item, _initialState);
					this.persister.Save(item);

					_tx.Commit();

					return _initialState;
				}
			}
			//For some reason workflow for item wasn't resolved..
			return null;
		}

		IEnumerable<ItemState> GetHistory(ContentItem item)
		{
			return item.GetChildren(new TypeFilter(typeof(ItemState)))
				.Cast<ItemState>();
		}

		#endregion Methods
	}
}
